package palms.client.manager;

import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Calendar;

import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SealedObject;
import javax.crypto.spec.SecretKeySpec;

import android.util.Log;
import palms.client.domain.User;
import palms.exceptions.InvalidClassMessage;
import palms.exceptions.InvalidPasswordMessage;
import palms.exceptions.InvalidTicketMessage;
import palms.exceptions.InvalidUserMessage;
import palms.messages.AuthRequest;
import palms.messages.AuthResponse;
import palms.messages.SIRequest;
import palms.messages.SIResponse;
import palms.messages.SIServiceInfo;
import palms.messages.SLRequest;
import palms.messages.SLServiceInfo;
import palms.messages.SLServiceResults;
import palms.messages.TGSClientInfo;
import palms.messages.TGSKeyChain;
import palms.messages.TGSRequest;
import palms.messages.TGSResponse;
import palms.security.PalmsSecurityManager;

public class ClientManager {
	
	private User user;
	private int KASport = 56421;
	private int KTGSport = 56422;
	private int SIport = 56423;
	private String KASip = "10.0.2.2";
	private String KTGSip = "10.0.2.2";
	private String SIip = "10.0.2.2";
	
	public ClientManager(User user){
		this.setUser(user);
	}
	
	public void authenticate() throws InvalidUserMessage, InvalidPasswordMessage, InvalidTicketMessage{
		
		Socket s;
	
		try {
		
			//ENVIA 1
			AuthRequest authreq = new AuthRequest(this.getUser().getUsername());
			
			Log.d("PALMs", "Requesting authetication for user: " + authreq.getUsername());

			s = new Socket(KASip, KASport);
			authreq.sendTroughSocket(s);
			
			//RECEBE 2
	
			AuthResponse ar = AuthResponse.receiveTroughSocket(s);
			s.close();
		
			SealedObject ticket_tgs = ar.getTgsTicket();
			SealedObject sealed_tgssessionkey = ar.getKey();
			
			Log.d("PALMs", "Recieved response from AS");
			
			SecretKeySpec mykey = PalmsSecurityManager.getKeyFromString(this.getUser().getPassword());
			Log.d("PALMs", "Client secret key is: " + PalmsSecurityManager.asHex(mykey.getEncoded()));
			
			Log.d("PALMs", "TGS ticket: " + ticket_tgs);
			Log.d("PALMs", "Sealed TGS session key: " + sealed_tgssessionkey);
			
			SecretKeySpec tgssessionkey = (SecretKeySpec) PalmsSecurityManager.DesealObjectWithK(sealed_tgssessionkey, mykey);
			Log.d("PALMs", "Client/TGS session key: " + PalmsSecurityManager.asHex(tgssessionkey.getEncoded()));
					
			this.getUser().setTicket_tgs(ticket_tgs);
			this.getUser().setTgssessionkey(tgssessionkey);
						
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvalidKeyException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NoSuchAlgorithmException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvalidUserMessage e) {
			throw e;
		} catch (InvalidClassMessage e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvalidTicketMessage e) {
			throw e;
		} catch (NoSuchPaddingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalBlockSizeException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (BadPaddingException e) {
			throw new InvalidPasswordMessage();
		}
		
	}
	
	public void getTickets() throws InvalidTicketMessage{
		
		Socket s;
		
		try {
						
			//envia 3
			TGSClientInfo clientinfo = new TGSClientInfo(this.getUser().getUsername(), Calendar.getInstance().getTimeInMillis());
			SealedObject sealed_clientinfo = PalmsSecurityManager.SealObjectWithK(clientinfo, this.getUser().getTgssessionkey());
			
			Log.d("PALMs", "Requesting tickets for Id and Location Server");
			
			Log.d("PALMs", "TGS Ticket: " + this.user.getTicket_tgs());
			Log.d("PALMs", "Client info: " + sealed_clientinfo);
			Log.d("PALMs", "Username: \"" + clientinfo.getUsername() + "\"");
			Log.d("PALMs", "Timestamp: " + clientinfo.getTimestamp());
			
			TGSRequest tgsrequest = new TGSRequest(this.user.getTicket_tgs(), sealed_clientinfo);
			
			s = new Socket(KTGSip, this.KTGSport);
			tgsrequest.sendTroughSocket(s);

			//recebe 4
			TGSResponse tgsresponse = TGSResponse.receiveTroughSocket(s);
			s.close();
					
			this.getUser().setTicket_si(tgsresponse.getTicketSI());
			this.getUser().setTicket_sl(tgsresponse.getTicketSL());
			SealedObject sealed_keychain = tgsresponse.getKeyChain();
			
			TGSKeyChain keychain = (TGSKeyChain) PalmsSecurityManager.DesealObjectWithK(sealed_keychain, this.getUser().getTgssessionkey());
			
			Log.d("PALMs", "Recieved response from TGS");
			Log.d("PALMs", "SI ticket: " + tgsresponse.getTicketSI());
			Log.d("PALMs", "SL ticket: " + tgsresponse.getTicketSL());
			Log.d("PALMs", "Sealed SI and SL session keys: " + sealed_keychain);
			Log.d("PALMs", "Client/SI session key: " + PalmsSecurityManager.asHex(keychain.getClientSIkey().getEncoded()));
			Log.d("PALMs", "Client/TGS session key: " + PalmsSecurityManager.asHex(keychain.getClientSLkey().getEncoded()));
			
			this.getUser().setSisessionkey(keychain.getClientSIkey());
			this.getUser().setSlsessionkey(keychain.getClientSLkey());
		
		} catch (UnknownHostException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvalidKeyException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NoSuchAlgorithmException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalBlockSizeException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NoSuchPaddingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvalidTicketMessage e) {
			throw e;
		} catch (InvalidClassMessage e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvalidUserMessage e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (BadPaddingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}
	
	public String getService(){
		

		Socket s;

		try{
			
			Log.d("PALMs", "Requesting Location Based Service");
			//envia 5
			//Constroi pedido para o SL
			SLServiceInfo slsinfo = new SLServiceInfo(Calendar.getInstance().getTimeInMillis(), this.getUser().getService(), this.getUser().getLatitude(), this.getUser().getLongitude());
			SealedObject sealed_slsinfo = PalmsSecurityManager.SealObjectWithK(slsinfo, this.getUser().getSlsessionkey());

			SLRequest slreq = new SLRequest(this.getUser().getTicket_sl(), sealed_slsinfo);
	
			//Constroi pedido para o SI

			SIServiceInfo siinfo = new SIServiceInfo(this.getUser().getUsername(), Calendar.getInstance().getTimeInMillis(), slreq);
			SealedObject sealed_siinfo = PalmsSecurityManager.SealObjectWithK(siinfo, this.getUser().getSisessionkey());

			SIRequest sireq = new SIRequest(this.getUser().getTicket_si(), sealed_siinfo);

			Log.d("PALMs", "SI Ticket: " + this.user.getTicket_si());
			Log.d("PALMs", "Client info: " + sealed_siinfo);
			Log.d("PALMs", "Username: \"" + siinfo.getUsername() + "\"");
			Log.d("PALMs", "Timestamp: " + siinfo.getTimestamp());
			Log.d("PALMs", "SL Request: " + siinfo.getSlservice());
			Log.d("PALMs", "SL Ticket: " + slreq.getSlticket());
			Log.d("PALMs", "Service Info: " + slreq.getServiceinfo());
			Log.d("PALMs", "Latitude: " + slsinfo.getLat());
			Log.d("PALMs", "Longitude: " + slsinfo.getLog());
			Log.d("PALMs", "Timestamp: " + slsinfo.getTimestamp());
			Log.d("PALMs", "Type of service: " + slsinfo.getType());
	
			
			//envia pedido para o SI
			s = new Socket(SIip, SIport);
			sireq.sendTroughSocket(s);

			//recebe 8
			SIResponse siresp = SIResponse.receiveTroughSocket(s);
			s.close();

			SealedObject sealed_slresp = (SealedObject) siresp.getResult();
			SealedObject sealed_res =  (SealedObject) PalmsSecurityManager.DesealObjectWithK(sealed_slresp, this.getUser().getSisessionkey());
			SLServiceResults res = (SLServiceResults) PalmsSecurityManager.DesealObjectWithK(sealed_res, this.getUser().getSlsessionkey());
			
			Log.d("PALMs", "Recieved response from SI");
			Log.d("PALMs", "Sealed SI Service Response: " + sealed_slresp);
			Log.d("PALMs", "Sealed SL Service Response: " + sealed_res);
			Log.d("PALMs", "Service Response: Found " + res.getResult().size() + " results");

			this.getUser().setRes(res.getResult());

			return "Success";
		
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvalidKeyException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalBlockSizeException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NoSuchAlgorithmException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NoSuchPaddingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (BadPaddingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvalidClassMessage e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvalidUserMessage e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvalidTicketMessage e) {
			try{
				this.authenticate();
				this.getTickets();
			} catch (InvalidTicketMessage e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			} catch (InvalidUserMessage e1) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (InvalidPasswordMessage e1) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		return null;
		
	}

	public User getUser() {
		return user;
	}

	public void setUser(User user) {
		this.user = user;
	}
}
